Week 9 of 16

Build: Wire It Into the App

Replace the loose helpers.py functions with the PromptLibrary class in vault.py and app.py.

Day 45 75 minutes Build

Day 45 of 80

What You're Doing Today

You have two new classes — Prompt and PromptLibrary. Now you put them to work. Today you update the CLI tool (vault.py) to use them instead of raw dictionaries and loose functions.

The app's behavior won't change. The code will be significantly cleaner.

Update vault.py

Replace the contents of vault.py with this version. Notice how much cleaner the main loop is now that the library manages itself:

vault.py — updated to use classes Python
import anthropic
from dotenv import load_dotenv
from models import Prompt, PromptLibrary

load_dotenv()
client = anthropic.Anthropic()

# One object manages everything. No more importing load_prompts/save_prompts.
library = PromptLibrary("prompts.json")


def generate_prompt_text(shot, platform):
    """Call Claude to generate a prompt. Returns the text."""
    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=300,
        system="You are an expert AI video prompt engineer.",
        messages=[{
            "role": "user",
            "content": f"Write a production-ready {platform} AI video prompt for: {shot}. "
                       f"Include camera, lighting, mood. Under 100 words. Return only the prompt."
        }]
    )
    return message.content[0].text


print("=== DVP Prompt Vault ===\n")

while True:
    print(f"Library: {len(library)} prompts")
    print("1. View all  2. Generate  3. Search  4. Filter  5. Delete  6. Quit")
    choice = input("\nChoose: ")

    if choice == "1":
        if not library.prompts:
            print("\n  (empty)\n")
        else:
            for i, p in enumerate(library):
                # p is a Prompt object — print(p) uses __str__
                print(f"  {i + 1}. {p}")
                print(f"     {p.preview(60)}\n")

    elif choice == "2":
        shot = input("Describe your shot: ")
        raw_platform = input("Platform (Kling / Runway / Veo): ")

        try:
            text = generate_prompt_text(shot, raw_platform)
            print(f"\n{text}\n")

            if input("Save? (y/n): ").lower() == "y":
                # Prompt() validates the platform — raises ValueError if invalid
                prompt = Prompt(raw_platform, shot, text)
                library.add(prompt)
                print("Saved!\n")
        except ValueError as e:
            print(f"  {e}\n")
        except Exception as e:
            print(f"  API error: {e}\n")

    elif choice == "3":
        query = input("Search: ")
        results = library.search(query)
        print(f"\n  {len(results)} match{'es' if len(results) != 1 else ''}")
        for p in results:
            print(f"    {p}")
        print()

    elif choice == "4":
        platform = input("Filter by platform (Kling / Runway / Veo): ")
        results = library.filter_by_platform(platform)
        print(f"\n  {len(results)} {platform} prompts")
        for p in results:
            print(f"    {p}")
        print()

    elif choice == "5":
        idx = input("Delete prompt # (or 'cancel'): ")
        if idx.lower() != "cancel":
            try:
                removed = library.delete(int(idx) - 1)
                if removed:
                    print(f"  Deleted: {removed}\n")
                else:
                    print("  Invalid number.\n")
            except ValueError:
                print("  Enter a number.\n")

    elif choice == "6":
        print("Bye!")
        break

The main loop is simpler now. Instead of calling load_prompts() at the start and save_prompts() after every change, the library handles persistence internally. You just call library.add() or library.delete() and the file stays in sync.

Notice how validation moved. In the old code, platform validation was scattered across different functions. Now Prompt(raw_platform, shot, text) raises a ValueError if the platform is wrong — the class enforces its own rules.

for i, p in enumerate(library): works because you added __iter__ to PromptLibrary. The library behaves like a list. Clean, readable, Pythonic.

Stretch: Update app.py

If you want the bonus challenge, update your Flask app.py to use PromptLibrary too. The key change is switching from load_prompts() / save_prompts() to a PromptLibrary instance. Here's the pattern:

app.py — key changes only Python
# Before (helpers.py approach):
from helpers import load_prompts, save_prompts
prompts = load_prompts()
# ... do stuff ...
save_prompts(prompts)

# After (PromptLibrary approach):
from models import Prompt, PromptLibrary
library = PromptLibrary("prompts.json")
# ... do stuff ...
# no manual save — library.add() and library.delete() handle it

# In your routes, access prompts with:
library.prompts     # the full list
library.search(q)   # search results
library.filter_by_platform(p)  # filtered list
library.platform_counts()     # for the stats bar

# In Jinja2 templates, switch from p['platform'] to p.platform
# (dot notation works because Prompt objects have attributes, not keys)
Week 9 Complete

You now think in objects. A Prompt isn't just a dictionary — it validates itself, formats itself, and serializes itself. A PromptLibrary manages a collection with a clean API.

This is the shift from "scripting" to "software engineering."

End of Week Checklist